Personalize notificações de mídia e controles de mídia com a API Media Session

Como integrar com teclas de mídia de hardware, personalizar notificações de mídia e muito mais.

François Beaufort
François Beaufort

Para que os usuários saibam o que está sendo reproduzido no navegador e o controlem sem retornar à página que o iniciou, a API Media Session foi introduzida. Ele permite que os desenvolvedores da Web personalizem essa experiência com metadados em notificações de mídia personalizadas, eventos de mídia, como reprodução, pausa, busca, mudança de faixa, e eventos de videoconferência, como silenciar/desativar microfone, ativar/desativar câmera e desligar. Essas personalizações estão disponíveis em vários contextos, incluindo hubs de mídia para computadores, notificações de mídia em dispositivos móveis e até mesmo em dispositivos vestíveis. Vou descrever essas personalizações neste artigo.

Capturas de tela de contextos de sessão de mídia.
Hub de mídia no computador, notificação de mídia em dispositivos móveis e em um wearable.

Sobre a API Media Session

A API Media session oferece vários benefícios e recursos:

  • Chaves de mídia de hardware são aceitas.
  • As notificações de mídia são personalizadas em dispositivos móveis, computadores e wearables pareados.
  • O hub de mídia está disponível em computadores.
  • Os controles de mídia da tela de bloqueio estão disponíveis no ChromeOS e em dispositivos móveis.
  • Os controles da janela Picture-in-Picture estão disponíveis para reprodução de áudio, conferências em vídeo e apresentação de slides.
  • A integração do Google Assistente em dispositivos móveis está disponível.

Browser Support

  • Chrome: 73.
  • Edge: 79.
  • Firefox: 82.
  • Safari: 15.

Source

Alguns exemplos vão ilustrar alguns desses pontos.

Exemplo 1:se os usuários pressionarem a tecla de mídia "next track" do teclado, os desenvolvedores da Web poderão processar essa ação do usuário, mesmo que o navegador esteja em primeiro plano ou em segundo plano.

Exemplo 2:se os usuários ouvirem um podcast na Web enquanto a tela do dispositivo estiver bloqueada, eles ainda poderão tocar no ícone "procurar para trás" nos controles de mídia da tela de bloqueio para que os desenvolvedores da Web movam o tempo de reprodução para trás por alguns segundos.

Exemplo 3:se os usuários tiverem guias que estão reproduzindo áudio, eles poderão interromper a reprodução facilmente no hub de mídia no computador para que os desenvolvedores da Web possam limpar o estado.

Exemplo 4:se os usuários estiverem em uma videochamada, eles poderão pressionar o controle "Mudar microfone" na janela Picture-in-Picture para impedir que o site receba dados do microfone.

Tudo isso é feito por duas interfaces diferentes: a interface MediaSession e a interface MediaMetadata. O primeiro permite que os usuários controlem o que está sendo reproduzido. A segunda é como você informa ao MediaSession o que precisa ser controlado.

Para ilustrar, a imagem abaixo mostra como essas interfaces se relacionam a controles específicos de mídia, neste caso, uma notificação de mídia em dispositivos móveis.

Ilustração das interfaces de sessão de mídia.
Anatomia de uma notificação de mídia em dispositivos móveis.

Informe aos usuários o que está sendo reproduzido

Quando um site está reproduzindo áudio ou vídeo, os usuários recebem automaticamente notificações de mídia na bandeja de notificações em dispositivos móveis ou no hub de mídia em computadores. O navegador faz o possível para mostrar as informações adequadas usando o título do documento e a imagem de ícone maior que ele consegue encontrar. Com a API Media Session, é possível personalizar a notificação de mídia com alguns metadados de mídia mais ricos, como título, nome do artista, nome do álbum e arte, conforme mostrado abaixo.

O Chrome solicita o foco de áudio "total" para mostrar notificações de mídia somente quando a duração da mídia é de pelo menos 5 segundos. Isso garante que sons incidentais, como ruídos, não mostrem notificações.

// After media (video or audio) starts playing
await document.querySelector("video").play();

if ("mediaSession" in navigator) {
  navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    artist: 'Rick Astley',
    album: 'Whenever You Need Somebody',
    artwork: [
      { src: 'https://via.placeholder.com/96',   sizes: '96x96',   type: 'image/png' },
      { src: 'https://via.placeholder.com/128', sizes: '128x128', type: 'image/png' },
      { src: 'https://via.placeholder.com/192', sizes: '192x192', type: 'image/png' },
      { src: 'https://via.placeholder.com/256', sizes: '256x256', type: 'image/png' },
      { src: 'https://via.placeholder.com/384', sizes: '384x384', type: 'image/png' },
      { src: 'https://via.placeholder.com/512', sizes: '512x512', type: 'image/png' },
    ]
  });

  // TODO: Update playback state.
}

Quando a reprodução termina, não é necessário "liberar" a sessão de mídia, porque a notificação desaparece automaticamente. No entanto, navigator.mediaSession.metadata será usado quando a próxima reprodução começar. Por isso, é importante atualizar o conteúdo quando a fonte de reprodução de mídia mudar para garantir que as informações relevantes sejam mostradas na notificação de mídia.

Há algumas observações sobre os metadados de mídia.

  • A matriz de arte da notificação oferece suporte a URLs de blob e de dados.
  • Se nenhuma arte for definida e houver uma imagem de ícone (especificada usando <link rel=icon>) no tamanho desejado, as notificações de mídia vão usar essa imagem.
  • O tamanho de destino da arte da notificação no Chrome para Android é 512x512. Para dispositivos básicos, é 256x256.
  • O atributo title do elemento HTML de mídia é usado no widget "Now playing" do macOS.
  • Se o recurso de mídia estiver incorporado (por exemplo, em um iframe), as informações da API Media Session precisam ser definidas no contexto incorporado. Confira o snippet abaixo.
<iframe id="iframe">
  <video>...</video>
</iframe>
<script>
  iframe.contentWindow.navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    ...
  });
</script>

Você também pode adicionar informações de capítulos individuais, como o título da seção, o carimbo de data/hora e uma imagem de captura de tela aos metadados de mídia. Isso permite que os usuários naveguem pelo conteúdo da mídia.

navigator.mediaSession.metadata = new MediaMetadata({
  // title, artist, album, artwork, ...
  chapterInfo: [{
    title: 'Chapter 1',
    startTime: 0,
    artwork: [
      { src: 'https://via.placeholder.com/128', sizes: '128x128', type: 'image/png' },
      { src: 'https://via.placeholder.com/512', sizes: '512x512', type: 'image/png' },
    ]
  }, {
    title: 'Chapter 2',
    startTime: 42,
    artwork: [
      { src: 'https://via.placeholder.com/128', sizes: '128x128', type: 'image/png' },
      { src: 'https://via.placeholder.com/512', sizes: '512x512', type: 'image/png' },
    ]
  }]
});
Informações do capítulo exibidas em uma notificação de mídia do ChromeOS.
Notificação de mídia com capítulos no ChromeOS.

Permitir que os usuários controlem o que está tocando

Uma ação de sessão de mídia é uma ação (por exemplo, "reproduzir" ou "pausar") que um site pode processar para os usuários quando eles interagem com a reprodução de mídia atual. As ações são análogas e funcionam de maneira semelhante aos eventos. Assim como os eventos, as ações são implementadas definindo manipuladores em um objeto apropriado, uma instância de MediaSession, neste caso. Algumas ações são acionadas quando os usuários pressionam botões de um fone de ouvido, outro dispositivo remoto, um teclado ou interagem com uma notificação de mídia.

Captura de tela de uma notificação de mídia no Windows 10.
Notificação de mídia personalizada no Windows 10.

Como algumas ações de sessão de mídia podem não ser compatíveis, é recomendável usar um bloco try…catch ao configurá-las.

const actionHandlers = [
  ['play',          () => { /* ... */ }],
  ['pause',         () => { /* ... */ }],
  ['previoustrack', () => { /* ... */ }],
  ['nexttrack',     () => { /* ... */ }],
  ['stop',          () => { /* ... */ }],
  ['seekbackward',  (details) => { /* ... */ }],
  ['seekforward',   (details) => { /* ... */ }],
  ['seekto',        (details) => { /* ... */ }],
  /* Video conferencing actions */
  ['togglemicrophone', () => { /* ... */ }],
  ['togglecamera',     () => { /* ... */ }],
  ['hangup',           () => { /* ... */ }],
  /* Presenting slides actions */
  ['previousslide', () => { /* ... */ }],
  ['nextslide',     () => { /* ... */ }],
];

for (const [action, handler] of actionHandlers) {
  try {
    navigator.mediaSession.setActionHandler(action, handler);
  } catch (error) {
    console.log(`The media session action "${action}" is not supported yet.`);
  }
}

Desfazer a configuração de um gerenciador de ação de sessão de mídia é tão fácil quanto definir como null.

try {
  // Unset the "nexttrack" action handler at the end of a playlist.
  navigator.mediaSession.setActionHandler('nexttrack', null);
} catch (error) {
  console.log(`The media session action "nexttrack" is not supported yet.`);
}

Depois de definidos, os manipuladores de ação da sessão de mídia vão persistir durante as reproduções de mídia. Isso é semelhante ao padrão do listener de eventos, exceto que o processamento de um evento significa que o navegador para de fazer qualquer comportamento padrão e usa isso como um sinal de que o site oferece suporte à ação de mídia. Portanto, os controles de ação de mídia não serão mostrados, a menos que o manipulador de ação adequado seja definido.

Captura de tela do widget &quot;Agora tocando&quot; no macOS Big Sur.
Widget "Now Playing" no macOS Big Sur.

Reproduzir / pausar

A ação "play" indica que o usuário quer retomar a reprodução de mídia, enquanto "pause" indica que ele quer interromper temporariamente.

O ícone "reproduzir/pausar" é sempre mostrado em uma notificação de mídia, e os eventos de mídia relacionados são processados automaticamente pelo navegador. Para substituir o comportamento padrão, processe as ações de mídia "play" e "pause", conforme mostrado abaixo.

O navegador pode considerar que um site não está reproduzindo mídia ao procurar ou carregar, por exemplo. Nesse caso, substitua esse comportamento definindo navigator.mediaSession.playbackState como "playing" ou "paused" para garantir que a interface do site permaneça sincronizada com os controles de notificação de mídia.

const video = document.querySelector('video');

navigator.mediaSession.setActionHandler('play', async () => {
  // Resume playback
  await video.play();
});

navigator.mediaSession.setActionHandler('pause', () => {
  // Pause active playback
  video.pause();
});

video.addEventListener('play', () => {
  navigator.mediaSession.playbackState = 'playing';
});

video.addEventListener('pause', () => {
  navigator.mediaSession.playbackState = 'paused';
});

Faixa anterior

A ação "previoustrack" indica que o usuário quer iniciar a reprodução de mídia atual do início, se a reprodução de mídia tiver uma noção de início, ou mover para o item anterior na playlist, se a reprodução de mídia tiver uma noção de playlist.

navigator.mediaSession.setActionHandler('previoustrack', () => {
  // Play previous track.
});

Próxima faixa

A ação "nexttrack" indica que o usuário quer mover a reprodução de mídia para o próximo item da playlist, se a reprodução de mídia tiver uma noção de playlist.

navigator.mediaSession.setActionHandler('nexttrack', () => {
  // Play next track.
});

Parar

A ação "stop" indica que o usuário quer interromper a reprodução de mídia e limpar o estado, se apropriado.

navigator.mediaSession.setActionHandler('stop', () => {
  // Stop playback and clear state if appropriate.
});

Retroceder / avançar

A ação "seekbackward" indica que o usuário quer mover o tempo de reprodução da mídia para trás por um curto período, enquanto "seekforward" indica o desejo de mover o tempo de reprodução da mídia para frente por um curto período. Em ambos os casos, um período curto significa alguns segundos.

O valor seekOffset fornecido no action handler é o tempo em segundos para mover a reprodução de mídia. Se ele não for fornecido (por exemplo, undefined), use um tempo razoável (por exemplo, 10 a 30 segundos).

const video = document.querySelector('video');
const defaultSkipTime = 10; /* Time to skip in seconds by default */

navigator.mediaSession.setActionHandler('seekbackward', (details) => {
  const skipTime = details.seekOffset || defaultSkipTime;
  video.currentTime = Math.max(video.currentTime - skipTime, 0);
  // TODO: Update playback state.
});

navigator.mediaSession.setActionHandler('seekforward', (details) => {
  const skipTime = details.seekOffset || defaultSkipTime;
  video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
  // TODO: Update playback state.
});

Buscar um horário específico

A ação "seekto" indica que o usuário quer mover o tempo de reprodução de mídia para um horário específico.

O valor seekTime fornecido no action handler é o tempo em segundos para mover a reprodução de mídia.

O booleano fastSeek fornecido no action handler é verdadeiro se a ação estiver sendo chamada várias vezes como parte de uma sequência e essa não for a última chamada naquela sequência.

const video = document.querySelector('video');

navigator.mediaSession.setActionHandler('seekto', (details) => {
  if (details.fastSeek && 'fastSeek' in video) {
    // Only use fast seek if supported.
    video.fastSeek(details.seekTime);
    return;
  }
  video.currentTime = details.seekTime;
  // TODO: Update playback state.
});

Definir posição de reprodução

Mostrar com precisão a posição de reprodução de mídia em uma notificação é tão simples quanto definir o estado da posição em um momento apropriado, conforme mostrado abaixo. O estado de posição é uma combinação da taxa de reprodução de mídia, duração e hora atual.

Captura de tela dos controles de mídia da tela de bloqueio no ChromeOS.
Controles de mídia da tela de bloqueio no ChromeOS.

A duração precisa ser informada e positiva. A posição precisa ser positiva e menor que a duração. A taxa de reprodução precisa ser maior que 0.

const video = document.querySelector('video');

function updatePositionState() {
  if ('setPositionState' in navigator.mediaSession) {
    navigator.mediaSession.setPositionState({
      duration: video.duration,
      playbackRate: video.playbackRate,
      position: video.currentTime,
    });
  }
}

// When video starts playing, update duration.
await video.play();
updatePositionState();

// When user wants to seek backward, update position.
navigator.mediaSession.setActionHandler('seekbackward', (details) => {
  /* ... */
  updatePositionState();
});

// When user wants to seek forward, update position.
navigator.mediaSession.setActionHandler('seekforward', (details) => {
  /* ... */
  updatePositionState();
});

// When user wants to seek to a specific time, update position.
navigator.mediaSession.setActionHandler('seekto', (details) => {
  /* ... */
  updatePositionState();
});

// When video playback rate changes, update position state.
video.addEventListener('ratechange', (event) => {
  updatePositionState();
});

É fácil redefinir o estado da posição para null.

// Reset position state when media is reset.
navigator.mediaSession.setPositionState(null);

Ações de videoconferência

Quando o usuário coloca a videochamada em uma janela picture-in-picture, o navegador pode mostrar controles para o microfone e a câmera, além de desligar a ligação. Quando o usuário clica nelas, o site as processa usando as ações de videoconferência abaixo. Para conferir um exemplo, consulte o exemplo de videoconferência.

Captura de tela dos controles de videoconferência em uma janela picture-in-picture.
Controles de videoconferência em uma janela picture-in-picture.

Ativar ou desativar o microfone

A ação "togglemicrophone" indica que o usuário quer ativar ou desativar o microfone. O método setMicrophoneActive(isActive) informa ao navegador se o site considera o microfone ativo no momento.

let isMicrophoneActive = false;

navigator.mediaSession.setActionHandler('togglemicrophone', () => {
  if (isMicrophoneActive) {
    // Mute the microphone.
  } else {
    // Unmute the microphone.
  }
  isMicrophoneActive = !isMicrophoneActive;
  navigator.mediaSession.setMicrophoneActive(isMicrophoneActive);
});

Abrir/fechar câmera

A ação "togglecamera" indica que o usuário quer ativar ou desativar a câmera ativa. O método setCameraActive(isActive) indica se o navegador considera o site como ativo.

let isCameraActive = false;

navigator.mediaSession.setActionHandler('togglecamera', () => {
  if (isCameraActive) {
    // Disable the camera.
  } else {
    // Enable the camera.
  }
  isCameraActive = !isCameraActive;
  navigator.mediaSession.setCameraActive(isCameraActive);
});

Desligar

A ação "hangup" indica que o usuário quer encerrar uma chamada.

navigator.mediaSession.setActionHandler('hangup', () => {
  // End the call.
});

Ações de apresentação de slides

Quando o usuário coloca a apresentação de slides em uma janela Picture-in-Picture, o navegador pode mostrar controles para navegar pelos slides. Quando o usuário clicar neles, o site vai processá-los pela API Media Session. Para um exemplo, consulte o exemplo de apresentação de slides.

Slide anterior

A ação "previousslide" indica que o usuário quer voltar ao slide anterior ao apresentar slides.

navigator.mediaSession.setActionHandler('previousslide', () => {
  // Show previous slide.
});

Browser Support

  • Chrome: 111.
  • Edge: 111.
  • Firefox: not supported.
  • Safari: not supported.

Próximo slide

A ação "nextslide" indica que o usuário quer ir para o próximo slide ao apresentar slides.

navigator.mediaSession.setActionHandler('nextslide', () => {
  // Show next slide.
});

Browser Support

  • Chrome: 111.
  • Edge: 111.
  • Firefox: not supported.
  • Safari: not supported.

Amostras

Confira alguns exemplos de sessão de mídia com a Blender Foundation e o trabalho de Jan Morgenstern.

Um screencast que ilustra a API Media Session.

Recursos